package com.ruoyi.service.impl;

import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.entity.JrFile;
import com.ruoyi.entity.JrPermission;
import com.ruoyi.entity.JrSecuritys;
import com.ruoyi.entity.JrVersion;
import com.ruoyi.entity.vo.FileUploadVO;
import com.ruoyi.mapper.FilesMapper;
import com.ruoyi.mapper.FolderMapper;
import com.ruoyi.mapper.PermissionMapper;
import com.ruoyi.mapper.VersionMapper;
import com.ruoyi.server.MinioService;
import com.ruoyi.service.FilesService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.io.AbstractResource;
import org.springframework.http.*;
import org.springframework.scheduling.annotation.Async;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

@Service
@Slf4j
public class FilesServiceImpl implements FilesService {

    @Autowired
    private FolderMapper folderMapper;
    @Autowired
    private FilesMapper filesMapper;
    @Autowired
    private VersionMapper versionMapper;
    @Autowired
    private MinioService minioService;
    @Autowired
    private PermissionMapper permissionMapper;

    @Value("${ai.address}")
    private String ragflowAddress;

    @Value("${ai.your_api_key}")
    private String ragflowApiKey;

    @Value("${minio.bucket-name}")
    private String bucketName;

    @Override
    @Transactional
    public int renameFile(Long fileId, String newName) {
        // 1. 获取文件信息（需要知道它在哪个文件夹）
        JrFile file = filesMapper.selectFileById(fileId);
        if (file == null) {
            throw new ServiceException("文件不存在或已被删除");
        }

        // 2. 检查文件名是否包含非法字符
        if (containsInvalidChars(newName)) {
            throw new ServiceException("文件名包含非法字符");
        }

        // 3. 检查同一文件夹下是否已存在同名文件（排除自身）
        int count = filesMapper.existsSameNameInFolder(
                file.getFolderId(),
                newName,
                fileId
        );

        if (count > 0) {
            throw new ServiceException("该文件名已存在");
        }

        // 4. 执行重命名
        return filesMapper.renameFile(fileId, newName);
    }

    private boolean containsInvalidChars(String name) {
        return name.contains("/") || name.contains("\\") || name.contains(":") ||
                name.contains("*") || name.contains("?") || name.contains("\"") ||
                name.contains("<") || name.contains(">") || name.contains("|");
    }

    @Override
    @Transactional
    public int deleteFile(Long fileId) {
        JrFile file = null;
        try {
            // 1. 获取文件信息，用于删除MinIO中的文件
            file = filesMapper.selectFileById(fileId);
            if (file == null) {
                log.warn("要删除的文件不存在，fileId: {}", fileId);
                return 0;
            }

            log.info("开始删除文件，fileId: {}, 文件路径: {}", fileId, file.getFilePath());
            
            // 2. 删除MinIO中的文件
            if (StringUtils.isNotEmpty(file.getFilePath())) {
                // 从完整URL中提取文件名
                String fileName = extractFileNameFromUrl(file.getFilePath());
                if (fileName != null) {
                    boolean minioDeleteResult = minioService.deleteFile(bucketName, fileName);
                    if (!minioDeleteResult) {
                        log.error("MinIO文件删除失败，fileId: {}, 文件名: {}", fileId, fileName);
                        throw new ServiceException("MinIO文件删除失败");
                    }
                    log.info("MinIO文件删除成功，fileId: {}, 文件名: {}", fileId, fileName);
                } else {
                    log.warn("无法从文件路径中提取文件名，fileId: {}, 文件路径: {}", fileId, file.getFilePath());
                }
            } else {
                log.warn("文件路径为空，跳过MinIO删除，fileId: {}", fileId);
            }
            
            // 3. 删除版本记录
            versionMapper.deleteVersionsByFileId(fileId);
            log.info("删除版本记录完成，fileId: {}", fileId);
            
            // 4. 删除数据库记录
            int result = filesMapper.deleteFileByIds(Collections.singletonList(fileId));
            log.info("删除数据库记录完成，fileId: {}, 结果: {}", fileId, result);
            
            return result;
        } catch (Exception e) {
            log.error("删除文件失败，fileId: {}, 文件信息: {}", fileId, file != null ? file.toString() : "null", e);
            throw new ServiceException("删除文件失败：" + e.getMessage());
        }
    }

    /**
     * 从URL中提取文件名
     * @param filePath 完整的文件URL路径
     * @return 文件名，如果无法提取则返回null
     */
    private String extractFileNameFromUrl(String filePath) {
        try {
            if (StringUtils.isEmpty(filePath)) {
                return null;
            }
            
            // 如果路径中包含最后一个斜杠，则取斜杠后的部分
            int lastSlashIndex = filePath.lastIndexOf('/');
            if (lastSlashIndex != -1 && lastSlashIndex < filePath.length() - 1) {
                return filePath.substring(lastSlashIndex + 1);
            }
            
            // 如果没有斜杠，则可能是直接的文件名
            return filePath;
        } catch (Exception e) {
            log.error("从URL提取文件名失败: {}", filePath, e);
            return null;
        }
    }

    @Override
    public List<JrSecuritys> getFilesByCurrentUserSecurity() {
        LoginUser loginUser = SecurityUtils.getLoginUser();
        return filesMapper.selectFilesByUserSecurity(loginUser.getUserId());
    }

    @Transactional
    @Override
    public AjaxResult uploadFile(MultipartFile[] files , @RequestPart FileUploadVO dto) {

        // 1. 获取当前用户
        LoginUser loginUser = SecurityUtils.getLoginUser();
        SysUser user = loginUser.getUser();

        List<String> documentIds = new ArrayList<>();  // 存储ragflow上传成功的文档ID
        List<String> minioPaths = new ArrayList<>();
        List<JrFile> successFiles = new ArrayList<>();

        try {
            // 2. 验证用户密级
            validateUploadParams(dto, user);

            for (MultipartFile file : files) {
                JrFile processedFile = processSingleFile(dto, file, user);
                //上传minio
                Map<String, String> minioResult = minioService.uploadFile(file, processedFile.getName());
                String documentId = uploadToRAGFlow(file, bucketName);

                // 更新版本表和文件记录
                updateVersionAndFile(processedFile, minioResult.get("fileUrl"), documentId, user.getUserId());
                documentIds.add(documentId);
                minioPaths.add(minioResult.get("fileUrl"));
                successFiles.add(processedFile);
            }

            // 异步触发解析
            if (!documentIds.isEmpty()) {
                parseDocumentsAsync(bucketName, documentIds);
            }

            return AjaxResult.success("文件上传成功")
                    .put("successCount", successFiles.size())
                    .put("filePaths", minioPaths);

            }catch(Exception e){
            rollbackFailedUpload(successFiles, documentIds);
            log.error("文件上传失败: {}", e.getMessage());
            return AjaxResult.error("文件上传失败: " + e.getMessage());

            }
        }

    private void rollbackFailedUpload(List<JrFile> successFiles, List<String> documentIds) {
        // 删除数据库记录
        if (!successFiles.isEmpty()) {
            List<Long> ids = successFiles.stream()
                    .map(JrFile::getId)
                    .collect(Collectors.toList());
            filesMapper.batchDelete(ids);
        }

        // 删除MinIO文件（需实现对应方法）
//        successFiles.forEach(file -> {
//            if (file.getFilePath() != null) {
//                minioService.deleteFile(file.getFilePath());
//            }
//        });

        // 删除已上传的文档
        if (!documentIds.isEmpty()) {
            deleteDocuments(bucketName, documentIds);
        }
    }

    private void validateUploadParams(FileUploadVO dto, SysUser user) {
        if (user.getSecurity() < dto.getSecurity()) {
            throw new AccessDeniedException("用户密级不足");
        }
        if (dto.getFolderId() != 0 && folderMapper.selectById(dto.getFolderId()) == null) {
            throw new IllegalArgumentException("目标文件夹不存在");
        }
    }

    private JrFile processSingleFile(FileUploadVO dto, MultipartFile file, SysUser user) {
        String originalFilename = file.getOriginalFilename();
        String finalFilename = resolveFileNameConflict(dto, originalFilename);

        // 创建文件记录
        JrFile newFile = new JrFile();
        newFile.setFolderId(dto.getFolderId());
        newFile.setName(finalFilename);
        newFile.setSecurity(dto.getSecurity());
        newFile.setCurrentVersion(1);
        newFile.setFType("file");
        newFile.setFileSize(file.getSize());
        newFile.setUserId(user.getUserId());
        newFile.setUpdateUser(user.getUserName());
        filesMapper.insertFile(newFile);

        //插入权限
        createViewPermission(newFile.getId());

        return newFile;
    }
    /**
     * 上传文件的同时默认增加为查看权限
     * @param fileId 文件ID
     */
    private void createViewPermission(Long fileId) {
        System.out.println("上传文件的同时默认增加为查看权限       :   "+fileId);
        SysUser user = SecurityUtils.getLoginUser().getUser();
        JrPermission permission = new JrPermission();
        permission.setRefType(0); // 0=文件类型
        permission.setRefId(fileId);
        permission.setUserId(user.getUserId());
        permission.setCanView(true); // 设置查看权限为1
        permission.setCanDelete(false);
        permission.setCanEdit(false);
        permission.setCanAdd(false);
        permission.setCreateTime(new Date());

        permissionMapper.insertPermission(permission);
    }

    private String resolveFileNameConflict(FileUploadVO dto, String originalFilename) {
        List<JrFile> sameNameFiles = filesMapper.selectByFolderIdAndName(dto.getFolderId(), originalFilename);
        if (CollectionUtils.isEmpty(sameNameFiles)) {
            return originalFilename;
        }

        if ("overwrite".equals(dto.getHandleType())) {
            JrFile existFile = sameNameFiles.get(0);
            existFile.setSecurity(dto.getSecurity());
            filesMapper.updateById(existFile);
            int newVersion = existFile.getCurrentVersion() + 1;
            createVersion(existFile, SecurityUtils.getUserId(), Integer.valueOf(newVersion), null);
            return originalFilename;
        } else {
            return generateUniqueFileName(originalFilename, dto.getFolderId());
        }
    }

    private String generateUniqueFileName(String originalName, Long folderId) {
        String baseName = originalName.substring(0, originalName.lastIndexOf("."));
        String extension = originalName.substring(originalName.lastIndexOf("."));
        int copyNumber = 1;

        while (true) {
            String newName = String.format("%s (%d)%s", baseName, copyNumber, extension);
            if (filesMapper.selectByFolderIdAndName(folderId, newName).isEmpty()) {
                return newName;
            }
            copyNumber++;
        }
    }

    private void updateVersionAndFile(JrFile file, String minioPath, String documentId, Long userId) {
        // 更新文件记录
        file.setRagDocumentId(documentId);
        file.setFilePath(minioPath);
        filesMapper.updateById(file);

        // 创建版本记录
        createVersion(file, userId, file.getCurrentVersion(), minioPath);
    }

    //保存版本信息
    private void createVersion(JrFile file,Long userId, Integer version, String filePath) {
        SysUser user=new SysUser();
        JrVersion versionRecord = new JrVersion();
        versionRecord.setFileId(file.getId());
        versionRecord.setVersion(version);
        versionRecord.setUploadUserId(userId);
        versionRecord.setUploadTime(new Date());
        versionRecord.setStoragePath(filePath); // 存储MinIO路径
        versionRecord.setUpdateUser(user.getUserName());
        versionMapper.insertVersion(versionRecord);
    }


    // 上传到ragflow 知识库中
    private String uploadToRAGFlow(MultipartFile file, String datasetId) throws IOException {
        String url = ragflowAddress + "/api/v1/datasets/" + datasetId + "/documents";

        System.out.println("------------上传到ragflow 知识库中-------------" + file    +" id :"   +datasetId);

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.MULTIPART_FORM_DATA);
        headers.set("Authorization", ragflowApiKey);

        MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
        body.add("file", new MultipartFileResource(file));
        body.add("parser_config", "{\"chunk_token_num\": 500, \"delimiter\": \"\\n\"}");

        // 增加请求日志
        log.debug("Using RAGFlow API Key: {}", ragflowApiKey);

        try {
            // 使用RestTemplate发起POST请求，并获取响应
            ResponseEntity<Map<String, Object>> response = new RestTemplate().exchange(
                    url,
                    HttpMethod.POST,
                    new HttpEntity<>(body, headers),
                    new ParameterizedTypeReference<Map<String, Object>>() {}
            );

            // 增加完整响应日志
            log.debug("RAGFlow response: Status={}, Body={}",
                    response.getStatusCode(),
                    response.getBody());

            log.debug("开始上传文件到数据集 {} | 文件名: {} | 大小: {} bytes",
                    datasetId, file.getOriginalFilename(), file.getSize());


            if (response.getStatusCode().is2xxSuccessful()) {
                Map<String, Object> body1 = response.getBody();
                if (body1 != null && "0".equals(String.valueOf(body1.get("code")))) {
                    List<Map<String,Object>> data = (List<Map<String,Object>>) body1.get("data");
                    if (!CollectionUtils.isEmpty(data)) {
                        return data.get(0).get("id").toString();
                    }
                }
            }
            throw new IOException("RAGFlow上传失败: " + response.getStatusCode());
        } catch (RestClientException e) {
            throw new IOException("RAGFlow请求异常: " + e.getMessage(), e);
        }
    }

    // 异步解析方法（ragflow）
    @Async
    public CompletableFuture<Void> parseDocumentsAsync(String datasetId, List<String> documentIds) {
        return CompletableFuture.runAsync(() -> {
            try {
                // 调用解析接口
                String parseUrl = ragflowAddress + "/api/v1/datasets/" + datasetId + "/chunks";
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                headers.set("Authorization", ragflowApiKey);

                Map<String, Object> requestBody = Collections.singletonMap("document_ids", documentIds);

                ResponseEntity<Map> response = new RestTemplate().postForEntity(
                        parseUrl,
                        new HttpEntity<>(requestBody, headers),
                        Map.class
                );

                if (!response.getStatusCode().is2xxSuccessful() ||
                        !"0".equals(String.valueOf(response.getBody().get("code")))) {
                    throw new RuntimeException("解析请求失败");
                }

                // TODO: 这里添加轮询解析状态的逻辑（需要RAGFlow提供状态接口）

            } catch (Exception e) {
                // 解析失败时删除文档
                deleteDocuments(datasetId, documentIds);
                log.error("文档解析失败，已清理上传文件", e);
            }
        });
    }


    // 文档删除实现
    private void deleteDocuments(String datasetId, List<String> documentIds) {
        String url = ragflowAddress + "/api/v1/datasets/" + datasetId + "/documents";

        System.out.println("---------------文档删除实现------------ " + datasetId        +"文档id "+  documentIds);


        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.set("Authorization", ragflowApiKey);

        Map<String, Object> body = Collections.singletonMap("ids", documentIds);

        new RestTemplate().exchange(
                url,
                HttpMethod.DELETE,
                new HttpEntity<>(body, headers),
                Void.class
        );
    }

    // 辅助类和方法
    private static class MultipartFileResource extends AbstractResource {
        private final MultipartFile file;

        public MultipartFileResource(MultipartFile file) {
            this.file = file;
        }

        @Override
        public String getDescription() {
            return "MultipartFile resource [" + file.getOriginalFilename() + "]";
        }

        @Override
        public String getFilename() {
            return file.getOriginalFilename();
        }

        @Override
        public long contentLength() {
            return file.getSize();
        }

        @Override
        public InputStream getInputStream() throws IOException {
            return file.getInputStream();
        }

        @Override
        public boolean exists() {
            return !file.isEmpty();
        }
    }
}
